package top.fullj.remoting.provider;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.fullj.remoting.codec.Hassian2Decoder;
import top.fullj.remoting.codec.Hassian2Encoder;

import java.nio.ByteOrder;

/**
 * @author bruce.wu
 * @since 2022/2/14 15:25
 */
public class NetworkServer {

    private static final Logger LOG = LoggerFactory.getLogger(NetworkServer.class);

    private final NetworkConfig config;

    private final EventLoopGroup group;

    private final Channel accepter;

    public NetworkServer(NetworkConfig config) {
        this.config = config;
        this.group = new NioEventLoopGroup();
        this.accepter = start();
    }

    public Channel start() {
        LOG.info("starting server: {}", config);
        try {
            return new ServerBootstrap()
                    .group(group)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 64)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new ServerInitializer())
                    .bind(config.getHost(), config.getPort())
                    .sync()
                    .channel();
        } catch (InterruptedException e) {
            throw new RuntimeException("start server failed" + config, e);
        }
    }

    public void shutdown() {
        if (this.accepter != null) {
            this.accepter.closeFuture().syncUninterruptibly();
        }
        this.group.shutdownGracefully();
        LOG.info("server shutdown: {}", config);
    }

    private static class ServerInitializer extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();

            // binary codec
            pipeline.addLast(new LengthFieldBasedFrameDecoder(
                    ByteOrder.BIG_ENDIAN, Short.MAX_VALUE,
                    0, 2,
                    0, 2, true));
            pipeline.addLast(new LengthFieldPrepender(ByteOrder.BIG_ENDIAN,
                    2, 0, false));

            pipeline.addLast(new LoggingHandler(LogLevel.TRACE));

            pipeline.addLast(new Hassian2Decoder());
            pipeline.addLast(new Hassian2Encoder());

            // message handler
            pipeline.addLast(new ServiceHandler());
        }
    }

}
